package com.chuangke.lowcode.component.query;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.chuangke.common.constant.ColumnConstants;
import com.chuangke.common.exception.ChuangkeException;
import com.chuangke.common.trans.annotation.TransResult;
import com.chuangke.common.utils.SpringContextUtil;
import com.chuangke.framework.model.BillIItemModel;
import com.chuangke.framework.model.BillItemModel;
import com.chuangke.framework.model.BillModel;
import com.chuangke.lowcode.LowCodeContext;
import com.chuangke.lowcode.component.ProgressComponent;
import com.chuangke.lowcode.model.EntityModel;

/**
 * 根据单据编号加载单据
 */
@Component
@Transactional(rollbackFor = Throwable.class)
public class BillLoadComponent implements ProgressComponent {

    public void process(LowCodeContext modelContext) {
        EntityModel billHeaderModel = modelContext.getEntityModel();

        Map<String, List> modelDatas = new HashMap<>();//key:模型Id；value:模型数据

        //根据单据编号，将所有的单据模型的数据加载到modelDatas中
        loadModelDatas(Collections.singletonList(modelContext.getEntityModel()), modelContext.getDatas(), modelDatas);

        List billHeaderDatas = modelDatas.get(billHeaderModel.getId());

        mountModelDatas(billHeaderDatas, modelDatas);

        //翻译名称映射字段
        billHeaderDatas = ((BillLoadComponent) AopContext.currentProxy()).transResult(billHeaderDatas) ;

        modelContext.setResult(billHeaderDatas);
    }

    @TransResult
    public List transResult(List datas){
        return datas ;
    }

    /**
     * 根据单据编号，加载所有单据模型的数据
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    private void loadModelDatas(List<EntityModel> models, List<String> billIdList, Map<String, List> modelDatas) {
        for (EntityModel model : models) {
            IService service = (IService) SpringContextUtil.getBean(model.getServiceBeanName());
            QueryWrapper qw = new QueryWrapper();
            qw.in(ColumnConstants.COL_BILL_ID, billIdList);
            modelDatas.put(model.getId(), service.list(qw));

            if (!CollectionUtils.isEmpty(model.getChildren())) {
                loadModelDatas(model.getChildren(), billIdList, modelDatas);
            }
        }
    }

    /**
     * 将所有的明细数据根据单据关联关系挂载到单据数据上
     *
     * @param billDatas
     * @param modelDatas
     */
    private void mountModelDatas(List billDatas, Map<String, List> modelDatas) {
        if (CollectionUtils.isEmpty(billDatas)) {
            return;
        }

        //获取需要挂账的明细字段，对应着实体的字段（List<明细实体> 明细s）
        Map<String, Field> map = getItemsField(billDatas.get(0), modelDatas.keySet());
        if (MapUtils.isEmpty(map)) {
            return;
        }

        for (Object o : billDatas) {
            for (String propertyModelId : map.keySet()) {
                List datas = getPropertyModelDatas(o,propertyModelId,modelDatas) ;

                if (CollectionUtils.isEmpty(datas)) {
                    continue ;
                }

                Field field = map.get(propertyModelId);
                field.setAccessible(true);
                try {
                    field.set(o, datas);
                } catch (IllegalAccessException e) {
                    throw new ChuangkeException(e);
                }

                mountModelDatas(datas, modelDatas);
            }
        }
    }

    private List getPropertyModelDatas(Object parentObject,String propertyModelId,Map<String, List> modelDatas){
        List datas = modelDatas.get(propertyModelId);
        if (CollectionUtils.isEmpty(datas)) {
            return null ;
        }

        //从最明细模型开始判断
        if (parentObject instanceof BillItemModel) {//明细数据挂账分录数据
        	BillItemModel billItemModel = (BillItemModel)parentObject;
            datas = (List) datas.stream().filter(d -> {
                BillIItemModel billIitemModel = (BillIItemModel) d;
                return billIitemModel.getBillItemId().equals(billItemModel.getBillItemId()) && billIitemModel.getBillId().equals(billItemModel.getBillId());
            }).sorted(Comparator.comparing(BillIItemModel::getBillIitemId)).collect(Collectors.toList());

        } else if (parentObject instanceof BillModel) {//表头数据挂账明细数据
        	BillModel billModel = (BillModel)parentObject ;
            datas = (List) datas.stream().filter(d -> {
                BillItemModel billItemModel = (BillItemModel) d;
                return billItemModel.getBillId().equals(billModel.getBillId());
            }).sorted(Comparator.comparing(BillItemModel::getBillItemId)).collect(Collectors.toList());
        } else {
            throw new ChuangkeException("未实现的类型");
        }

        return datas ;
    }

    /**
     * 获取有数据的模型字段
     * @param object
     * @param modelIds
     * @return
     */
    private Map<String, Field> getItemsField(Object object, Collection<String> modelIds) {
        Map<String, Field> map = new HashMap<>();
        Field[] fields = object.getClass().getDeclaredFields();

        for (Field f : fields) {
            if (f.getType() != java.util.List.class) {
                continue;
            }

            // 如果是List类型，得到其泛型(Generic)的类型
            Type genericType = f.getGenericType();
            if (genericType == null || !(genericType instanceof ParameterizedType)) {
                continue;
            }
            ParameterizedType pt = (ParameterizedType)genericType ;
            Class genericClazz = (Class) pt.getActualTypeArguments()[0];// 得到泛型里的class类型对象

            if (modelIds.contains(genericClazz.getSimpleName())) {
                map.put(genericClazz.getSimpleName(), f);
            }
        }
        return map;
    }

}